package com.googlecode.horncomb.orm.ibatis;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import static org.apache.commons.lang.Validate.*;

import org.springframework.dao.DataAccessException;
import org.springframework.orm.ObjectRetrievalFailureException;

import com.googlecode.horncomb.orm.id.generator.IdGenerator;
import com.googlecode.horncomb.orm.id.generator.JdkUUIDGenerator;
import com.googlecode.horncomb.orm.id.util.IdentifiableUtil;
import com.googlecode.horncomb.orm.support.filter.Condition;
import com.googlecode.horncomb.orm.support.filter.MapConditionImpl;
import com.googlecode.horncomb.orm.support.order.Order;
import com.googlecode.horncomb.orm.support.paged.CollectionWarpper;
import com.googlecode.horncomb.orm.support.paged.CollectionWrapperImpl;
import com.googlecode.horncomb.orm.support.paged.LimitPagedList;
import com.googlecode.horncomb.orm.support.paged.PagedList;

/**
 * SqlMap entity DAO implement. <br/>
 * @author Daniel
 */
public class SqlMapEntityDaoImpl<EntityType extends Serializable> extends
		AbstractSqlMapEntityDaoImpl<EntityType> {
	private static final long serialVersionUID = 2383392591630044311L;

	private String conditionParameterName = "condition"; //$NON-NLS-1$
	private String queryOrderParameterName = "queryOrder"; //$NON-NLS-1$
	private String pagedStartParameterName = "pagedStart"; //$NON-NLS-1$
	private String pagedEndParameterName = "pagedEnd"; //$NON-NLS-1$

	/** id field name */
	private String idName = "id"; //$NON-NLS-1$
	protected boolean useIdentifiableEntity = true;

	/**
	 * id generator, default is
	 * com.googlecode.horncomb.orm.id.impl.JdkUUIDGeneratorImpl
	 */
	private IdGenerator<?> idGenerator = new JdkUUIDGenerator();

	/**
	 * default constructor
	 */
	public SqlMapEntityDaoImpl() {
		super();
	}

	@Override
	protected void initDao() throws Exception {
		super.initDao();
		initIdLocation(false);
	}

	private void initIdLocation(boolean force) {
		if (useIdentifiableEntity) {
			IdentifiableUtil.initIdLocationCache(this.entityClass, this.idName,
					force);
			this.idName = IdentifiableUtil.getIdNameFromCache(this.entityClass);
		}
	}

	/**
	 * build statement name
	 * @param statementId
	 * @return statement name
	 */
	protected String buildStatementName(String simpleStmtName) {
		String resultId = simpleStmtName;
		if (this.useStatementNamespaces) {
			resultId = this.namespace + "." + simpleStmtName; //$NON-NLS-1$
		}
		return resultId;
	}

	// ----------------------------------------------------------------------

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.AbstractSqlMapEntityDaoImpl#getById(java.io.Serializable,
	 *      java.lang.String)
	 */
	@Override
	protected EntityType getById(Serializable id, String stmtName)
			throws DataAccessException {
		notNull(id);
		return this.getByQuery(new MapConditionImpl().addConstraint(this
				.getIdName(), id), stmtName);
	}

	/**
	 * @param cons
	 * @param stmtName
	 * @return
	 * @throws DataAccessException
	 */
	@Override
	@SuppressWarnings("unchecked")
	protected EntityType getByQuery(Condition cons, String stmtName)
			throws DataAccessException {
		notNull(cons);
		notEmpty(cons.getConstraintsMap());
		return (EntityType) this.getSqlMapClientTemplate().queryForObject(
				this.buildStatementName(StringUtils.defaultIfEmpty(stmtName,
						this.getStatementName)), this.buildParam(cons));
	}

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.AbstractSqlMapEntityDaoImpl#deleteById(java.io.Serializable,
	 *      java.lang.String)
	 */
	@Override
	protected void deleteById(Serializable id, String stmtName)
			throws DataAccessException {
		notNull(id);
		this.deleteByQuery(new MapConditionImpl().addConstraint(this
				.getIdName(), id), stmtName);
	}

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.AbstractSqlMapEntityDaoImpl#deleteById(java.io.Serializable,
	 *      java.lang.String)
	 */
	@Override
	protected void deleteByIds(Set<? extends Serializable> ids, String stmtName)
			throws DataAccessException {
		notEmpty(ids);
		this.deleteByQuery(new MapConditionImpl().addConstraint(this
				.getIdName()
				+ "s", ids), stmtName); //$NON-NLS-1$
	}

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.AbstractSqlMapEntityDaoImpl#deleteByQuery(com.googlecode.horncomb.orm.support.filter.Condition,
	 *      java.lang.String)
	 */
	@Override
	protected void deleteByQuery(Condition cons, String stmtName)
			throws DataAccessException {
		notNull(cons);
		notEmpty(cons.getConstraintsMap());
		this.getSqlMapClientTemplate().delete(
				this.buildStatementName(StringUtils.defaultIfEmpty(stmtName,
						this.deleteStatementName)), this.buildParam(cons));
	}

	/**
	 * insert a entity to database
	 * @param entity
	 *            the entity for inserting
	 * @param stmtName
	 *            the name of statement
	 * @throws DataAccessException
	 */
	@Override
	protected void insert(EntityType entity, String stmtName)
			throws DataAccessException {
		notNull(entity);
		try {
			notNull(this.idGenerator);
			IdentifiableUtil.setIdValue(entity, this.idGenerator.generateId());
			this.getSqlMapClientTemplate().insert(
					this.buildStatementName(StringUtils.defaultIfEmpty(
							stmtName, this.insertStatementName)), entity);
		} catch (DataAccessException dae) {
			try {
				IdentifiableUtil.setIdValue(entity, null);
			} catch (Exception ex) {
				// do nothing
			}
			throw dae;
		} catch (Exception ex) {
			this.logger.debug("set id error", ex);
		}
	}

	@Override
	protected void update(EntityType entity, String stmtName)
			throws DataAccessException {
		notNull(entity);
		this.getSqlMapClientTemplate().update(
				this.buildStatementName(StringUtils.defaultIfEmpty(stmtName,
						this.updateStatementName)), entity);
	}

	@Override
	protected boolean isNew(EntityType entity) {
		Serializable id = null;
		try {
			id = IdentifiableUtil.getIdValue(entity);
		} catch (Exception ex) {
			throw new ObjectRetrievalFailureException(this.entityClass, ex);
		}
		if (id == null || StringUtils.isBlank(id.toString())
				|| "0".equals(id.toString())) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * @param constraints
	 * @return
	 */
	protected Map<String, Object> buildParam(Condition constraints) {
		return this.buildParam(constraints, null);
	}

	/**
	 * @param constraints
	 * @param order
	 * @return
	 */
	protected Map<String, Object> buildParam(Condition constraints, Order order) {
		Map<String, Object> param = new HashMap<String, Object>();
		if (constraints != null) {
			param.put(this.conditionParameterName, constraints
					.getConstraintsMap());
		}
		if (order != null) {
			param.put(this.queryOrderParameterName, order.getSorts());
		}
		return param;
	}

	/**
	 * @param constraints
	 * @param order
	 * @param startIndex
	 * @param endIndex
	 * @return
	 */
	protected Map<String, Object> buildParam(Condition constraints,
			Order order, int startIndex, int endIndex) {
		Map<String, Object> param = this.buildParam(constraints, order);
		param.put(this.pagedStartParameterName, startIndex);
		param.put(this.pagedEndParameterName, endIndex);
		return param;
	}

	public Collection<EntityType> queryAll(Order order) {
		return this.query(null, order);
	}

	public PagedList<? extends Collection<EntityType>> queryAll(Order order,
			int page, int pageSize) {
		return this.query(null, order, page, pageSize);
	}

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.AbstractSqlMapEntityDaoImpl#query(com.googlecode.horncomb.orm.support.filter.Condition,
	 *      com.googlecode.horncomb.orm.support.order.Order, java.lang.String)
	 */
	@Override
	@SuppressWarnings("unchecked")
	protected Collection<EntityType> query(Condition constraints, Order order,
			String qryStmtName) throws DataAccessException {
		notEmpty(qryStmtName);
		CollectionWarpper<? extends Collection<?>> r = this.doQuery(
				constraints, order, 0, 0, qryStmtName, null);
		return (Collection<EntityType>) r.getSource();
	}

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.AbstractSqlMapEntityDaoImpl#query(com.googlecode.horncomb.orm.support.filter.Condition,
	 *      com.googlecode.horncomb.orm.support.order.Order, int, int,
	 *      java.lang.String, java.lang.String)
	 */
	@Override
	@SuppressWarnings("unchecked")
	protected PagedList<? extends Collection<EntityType>> query(
			Condition constraints, Order order, int page, int pageSize,
			String qryStmtName, String queryCountStmtId)
			throws DataAccessException {
		notEmpty(qryStmtName);
		notEmpty(queryCountStmtId);
		isTrue(page > 0);
		isTrue(pageSize > 0);
		CollectionWarpper<? extends Collection<?>> r = this.doQuery(
				constraints, order, page, pageSize, qryStmtName,
				queryCountStmtId);
		// always force result casted into PagedList
		return (PagedList) r;
	}

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.AbstractSqlMapEntityDaoImpl#queryCount(com.googlecode.horncomb.orm.support.filter.Condition,
	 *      java.lang.String)
	 */
	@Override
	protected int queryCount(Condition constraints, String qryCntStmtName) {
		return (Integer) this.getSqlMapClientTemplate().queryForObject(
				this.buildStatementName(qryCntStmtName),
				this.buildParam(constraints));
	}

	/**
	 * @param constraints
	 * @param order
	 * @param page
	 * @param pageSize
	 * @param qryStmtName
	 * @param qryCntStmtName
	 * @return
	 * @throws DataAccessException
	 */
	@SuppressWarnings("unchecked")
	protected CollectionWarpper<? extends Collection<?>> doQuery(
			Condition constraints, Order order, int page, int pageSize,
			String qryStmtName, String qryCntStmtName)
			throws DataAccessException {
		Map<String, Object> param = null;
		CollectionWarpper<List<?>> lplh;
		if (page > 0 && pageSize > 0) {
			// need paged
			int cnt = this.queryCount(constraints, StringUtils.defaultIfEmpty(
					qryCntStmtName, this.queryCountStatementName));
			lplh = new LimitPagedList<List<?>>(page, pageSize, cnt);

			// not find any record
			if (cnt == 0) {
				// return empty LimitPagedList
				lplh.setSource(new ArrayList<Object>());
				return lplh;
			}
			param = buildParam(constraints, order, ((LimitPagedList) lplh)
					.getFirstElementOnPage(), ((LimitPagedList) lplh)
					.getLastElementOnPage());
		} else {
			// no paged
			lplh = new CollectionWrapperImpl<List<?>>();
			param = this.buildParam(constraints, order);
		}
		List result = this.getSqlMapClientTemplate().queryForList(
				this.buildStatementName(StringUtils.defaultIfEmpty(qryStmtName,
						this.queryStatementName)), param);
		lplh.setSource(result);
		return lplh;
	}

	/**
	 * @param idGenerator
	 *            the idGenerator to set
	 */
	public void setIdGenerator(IdGenerator<?> idGenerator) {
		notNull(idGenerator);
		this.idGenerator = idGenerator;
	}

	public String getIdName() {
		return IdentifiableUtil.getIdNameFromCache(this.entityClass);
	}

	/**
	 * @param idName
	 *            the idName to set
	 */
	public void setIdName(String idName) {
		this.idName = idName;
		initIdLocation(true);
	}

	/**
	 * @param conditionParameterName
	 *            the conditionParameterName to set
	 */
	public void setConditionParameterName(String conditionParameterName) {
		notEmpty(conditionParameterName);
		this.conditionParameterName = conditionParameterName;
	}

	/**
	 * @param queryOrderParameterName
	 *            the queryOrderParameterName to set
	 */
	public void setQueryOrderParameterName(String queryOrderParameterName) {
		notEmpty(queryOrderParameterName);
		this.queryOrderParameterName = queryOrderParameterName;
	}

	/**
	 * @param pagedStartParameterName
	 *            the pagedStartParameterName to set
	 */
	public void setPagedStartParameterName(String pagedStartParameterName) {
		notEmpty(pagedStartParameterName);
		this.pagedStartParameterName = pagedStartParameterName;
	}

	/**
	 * @param pagedEndParameterName
	 *            the pagedEndParameterName to set
	 */
	public void setPagedEndParameterName(String pagedEndParameterName) {
		notEmpty(pagedStartParameterName);
		this.pagedEndParameterName = pagedEndParameterName;
	}

	/**
	 * @param useIdentifiableEntity
	 *            the useIdentifiableEntity to set
	 */
	public void setUseIdentifiableEntity(boolean needId) {
		this.useIdentifiableEntity = needId;
	}
}
